require 'json'
require 'yaml'
require 'hyper_resource'
require 'require_all'
require_rel './import_master'

# Imports Seiso data master files into Seiso.
#
# Author:: Willie Wheeler
# Copyright:: Copyright (c) 2014-2016 Expedia, Inc.
# License:: Apache 2.0
module Seiso
  class ImportMaster

    def initialize(seiso3_settings)
      @log = Util::Logger.new "ImportMaster"
      @loaders = {
        'json' => ->(file) { JSON.parse(IO.read(file)) },
        'yaml' => ->(file) { YAML.load_file file }
      }
      @v3_api = HyperResource.new(
        root: seiso3_settings['base_uri'],
        headers: {
          'Accept' => 'application/hal+json',
          'Content-Type' => 'application/hal+json'
        },
        auth: {
          basic: [ seiso3_settings['username'], seiso3_settings['password'] ]
        }
      )
      @resolver = Util::ItemResolver.new @v3_api
      @rest_util = Util::RestUtil.new

      # Importer cache, since they're expensive to initialize
      @importers = {}
    end

    # Imports a list of master files in order. Legal formats are 'json' (default) and 'yaml'.
    # Returns true if all file imports succeeded and false otherwise.
    def import_files(files, format = 'json')
      begin
        all_failed = true
        all_success = true
        loop do
          file = files.pop
          success = import_file(file, format)
          if success
            all_failed = false
          else
            all_success = false
          end
          break if files.empty?
        end
        if all_success
          return "success"
        elsif all_failed
          return "failed"
        else
          return "partial"
        end
      rescue Interrupt => e
        @log.error "Import interrupted."
        return "failed"
      end
    end

    private

    # Imports a data master file. Legal formats are 'json' (default) and 'yaml'.
    # Returns true if the import succeeded and false otherwise.
    def import_file(file, format = 'json')
      @log.info "Importing file #{file}"
      begin
        # Include this in the begin/rescue as it can raise a JSON::ParserError
        loader = @loaders[format]

        fail ArgumentError, "Illegal format: #{format}" if loader.nil?

        doc = loader.call file
        type = doc['type']
        get_importer(type).import doc
        return true

      # Rescue StandardError here, not Exception, since we want ctrl-c to stop the program.
      rescue StandardError => e

        # IMPORTANT: Don't display the message, because Faraday includes the request headers, which include the base64
        # encoded password.
        @log.error "Failed to import file #{file}: #{e.message}"
#        @log.error "Failed to import file #{file}"

        # FIXME For now, just re-raise the exception, as we want to see where the bugs are.
        return false
#        raise e
      end
    end

    def get_importer(type)
      # TODO Do a cleaner impl here--one that doesn't violate the open-closed principle. Note that we *don't* want to
      # create these eagerly, as each one involves an expensive initialization.
      importer = @importers[type]

      if importer.nil?
        importer = case type
        when 'data-centers'
          Importers::DataCenterImporter.new(@v3_api, @rest_util, @resolver)
        when 'environments'
          Importers::EnvironmentImporter.new(@v3_api, @rest_util, @resolver)
        when 'health-statuses'
          Importers::HealthStatusImporter.new(@v3_api, @rest_util, @resolver)
        when 'infrastructure-providers'
          Importers::InfrastructureProviderImporter.new(@v3_api, @rest_util, @resolver)
        when 'load-balancers'
          Importers::LoadBalancerImporter.new(@v3_api, @rest_util, @resolver)
        when 'machines'
          Importers::MachineImporter.new(@v3_api, @rest_util, @resolver)
        when 'nodes'
          Importers::NodeImporter.new(@v3_api, @rest_util, @resolver)
        when 'regions'
          Importers::RegionImporter.new(@v3_api, @rest_util, @resolver)
        when 'rotation-statuses'
          Importers::RotationStatusImporter.new(@v3_api, @rest_util, @resolver)
        when 'services'
          Importers::ServiceImporter.new(@v3_api, @rest_util, @resolver)
        when 'service-groups'
          Importers::ServiceGroupImporter.new(@v3_api, @rest_util, @resolver)
        when 'service-instances'
          Importers::ServiceInstanceImporter.new(@v3_api, @rest_util, @resolver)
        when 'service-types'
          Importers::ServiceTypeImporter.new(@v3_api, @rest_util, @resolver)
        when 'status-types'
          Importers::StatusTypeImporter.new(@v3_api, @rest_util, @resolver)
        else
          raise ArgumentError, "Illegal type: #{type}"
        end
        @importers['type'] = importer
      end

      importer
    end
  end
end
